#include "LZP.h"

LZP::LZP(int tamanoArchivo) {

	posicionDeInicioDelBloque = 0;
	posicionActualdeProceso = 0;
	indice = new Indice();
	manejador = new ManejadorDeArboles();
	cantidadBloques = tamanoArchivo / TAM_BLOQUE;
	bytesRestantes = tamanoArchivo % TAM_BLOQUE;

}

LZP::~LZP() {

	delete manejador;
	delete indice;
}

void LZP::codificarCaracterSinContexto(unsigned char caracter) {

	manejador->emitirCaracter(caracter);
	posicionActualdeProceso++;
}

void LZP::codificarCaracterConContexto1(unsigned char caracter) {

	manejador->emitirCaracter(caracter, bloque[posicionActualdeProceso-1]);
	posicionActualdeProceso++;
}

int LZP::calcularLongitudMatch(int posicionMatch,int limite) {
	int longMatch = 0;
	bool huboMatch = true;
	bool salir = false;

	if (posicionFueraDeLaVentana(posicionMatch)){
			archivoEntrada->seekg(posicionMatch,ios::beg);
			if (archivoEntrada->fail()) cout << "fail de seek en " << posicionMatch << endl;
		}

	//puedo llegar buscando match hasta el anteultimo char del bloque
	//el ultimo char, va a ser el 1ero que no matchea
	//(aun cuando si matchee, lo corto para que no se me vaya fuera del bloque.
	//irse fuera del bloque implica querer leer datos todavia no levantados en la ventana)
	//probado: implementar matchs interbloques no mejoro la compresion y empeoro levemente el rendimiento.
	while (posicionMatch + longMatch < posicionActualdeProceso && huboMatch && !salir) {

		if (posicionActualDeProcesoDentroDelBloque+longMatch < limite-1) {

			if (hayMatch(posicionMatch + longMatch,posicionActualDeProcesoDentroDelBloque + longMatch))
				longMatch++;

			else huboMatch = false;
		}

		else salir = true;//llegue al limite del bloque,tengo que parar de buscar matchs o me voy a salir del rango de bloque.

	}
	return longMatch;

}

bool LZP::posicionFueraDeLaVentana(int posicion){

	return (posicion < posicionDeInicioDelBloque);
}

bool LZP::hayMatch(int posMatchTabla, int posMatchActual){

	//posMatchTabla puede estar en cualquier parte del archivo, (siempre anterior a la posicion actual de proceso)
	//posMatchActual SIEMPRE esta dentro de la ventana de memoria.
	if (posicionFueraDeLaVentana(posMatchTabla))
		archivoEntrada->read(charLecturaEnBuscaDeMatch,1);//ya tengo hecho el seek en calcularLongitudMatch

	else charLecturaEnBuscaDeMatch[0] = bloque[posMatchTabla-posicionDeInicioDelBloque];

	return charLecturaEnBuscaDeMatch[0] == bloque[posMatchActual];
}

void LZP::emitirLongitudDeMatch(int longMatch) {

	while (longMatch >= 255) {
		manejador->emitirLongitud(255);
		longMatch -= 255;
	}

	manejador->emitirLongitud(longMatch);

}

bool LZP::esPosicionValida(int posicionDelMatch){
	return posicionDelMatch != -1;//ver la inicializacion del indice
}

void LZP::codificarDupla(int limite) {

	establecerContexto();

	int longitudDelMatch = 0; int longitudTemp = 0;
	int posicionDelMatch = indice->getPosicion(vectorContexto[0],vectorContexto[1]);


	if (esPosicionValida(posicionDelMatch)) {
		longitudDelMatch = longitudTemp = calcularLongitudMatch(posicionDelMatch,limite);
	}
	else longitudDelMatch = 0;

	emitirLongitudDeMatch(longitudDelMatch);

	contextoCompresion = bloque[posicionActualDeProcesoDentroDelBloque+longitudTemp-1];
	primerCaracterQueNoMatchea = bloque[posicionActualDeProcesoDentroDelBloque+longitudTemp];

	manejador->emitirCaracter(primerCaracterQueNoMatchea, contextoCompresion);
	indice->actualizar(vectorContexto[0],vectorContexto[1], posicionActualdeProceso);
	posicionActualdeProceso += longitudTemp+1;
	posicionActualDeProcesoDentroDelBloque = posicionActualdeProceso-posicionDeInicioDelBloque;
}

void LZP::comprimir(string rutaArchivo, BitHandlerOutput &pbho) {
	archivoEntrada = new ifstream(rutaArchivo.c_str(), ios::binary|ios::in);
	manejador->setBitHandlerOutput(pbho);  // Escribe los datos comprimidos


	archivoEntrada->read(bloque, TAM_BLOQUE);

	// Codifico los dos primeros caracteres
	codificarCaracterSinContexto(bloque[posicionActualdeProceso]);
	codificarCaracterConContexto1(bloque[posicionActualdeProceso]);
	indice->actualizar(bloque[0],bloque[1], 2);
	indice->actualizar(bloque[1],bloque[2], 3);

	posicionActualDeProcesoDentroDelBloque = posicionActualdeProceso;

	for (int i = 0; i < cantidadBloques; i++) {
		cout << "\rBloque " << i + 1 << " de " << cantidadBloques << "..." << flush;
		while (posicionActualDeProcesoDentroDelBloque < TAM_BLOQUE) {
			codificarDupla(TAM_BLOQUE);
		}

		posicionDeInicioDelBloque = posicionDeInicioDelBloque + TAM_BLOQUE;
		anteultimoCaracterDelBLoqueAnterior = bloque[TAM_BLOQUE-2];
		ultimoCaracterDelBLoqueAnterior = bloque[TAM_BLOQUE-1];

		archivoEntrada->seekg(posicionDeInicioDelBloque,ios::beg);
		if (archivoEntrada->fail()) cout << "fail posicionDeInicioDelBloque" << endl;
		archivoEntrada->read(bloque, TAM_BLOQUE);

		posicionActualDeProcesoDentroDelBloque = 0;

	}

	posicionActualDeProcesoDentroDelBloque = posicionActualdeProceso - posicionDeInicioDelBloque;//0 cuando hay varios bloques, 2 cuando no hay ningun bloque (el arch es menor al tam del bloque)

	//en el ultimo bloque, como lee menos, me quedan los flags en fail. tengo que resetearlos
	archivoEntrada->clear();

	while (posicionActualDeProcesoDentroDelBloque < bytesRestantes) {
		codificarDupla(bytesRestantes);
	}


	archivoEntrada->close();
	delete archivoEntrada;
	pbho.rellenarConCeros();

}

int LZP::recibirLongitudDeMatch(){
	int longitudDelMatch = 0; int longitudTemp = 0;

	while ((longitudTemp = manejador->obtenerLongitud()) >= 255) {
		longitudDelMatch += longitudTemp;
	}

	longitudDelMatch += longitudTemp;

	return longitudDelMatch;
}

void LZP::obtenerMatch(int longitudDelMatch){

	if (longitudDelMatch > 0) {

		int posicionMatch = indice->getPosicion(vectorContexto[0],vectorContexto[1]);

		if (!esPosicionValida(posicionMatch))
			cout << "el descomp dice que hay match, pero en la tabla no me figura que ese contexto haya sido visto antes" << endl;

		if (posicionFueraDeLaVentana(posicionMatch)){

			bufferMatchFueraDeVentana = new char[longitudDelMatch];
			archivoSalida->seekg(posicionMatch,ios::beg);
			archivoSalida->read(bufferMatchFueraDeVentana,longitudDelMatch);

			int gcount = 0;
			/* ocurre un fail cuando desde una posicion fuera de ventana, hago un read
			 * tan largo que llega al fin del archivo (en realidad no es el EOF,
			 * solo que todavia no escribi el siguiente bloque, lo tengo en memoria)
			 * Necesito esos bytes que no pude leer del archivo, pero no estan ahi.
			 * Estan el buffer, entonces procedo a resetear los flags con clear()
			 * y levanto los bytes restantes del bloque.
			 * */
			if (archivoSalida->fail()) {

				gcount = archivoSalida->gcount();
				archivoSalida->clear();
				int j = 0;

				for (int i = gcount; i < longitudDelMatch; i++) {
					bufferMatchFueraDeVentana[i] = bloque[j];
					j++;
				}
			}

			for (int i = 0; i < longitudDelMatch; i++)
				bloque[posicionActualDeProcesoDentroDelBloque+i] = bufferMatchFueraDeVentana[i];

		delete bufferMatchFueraDeVentana;

		bufferMatchFueraDeVentana = NULL;
	}

		else {//MATCH DENTRO DE LA VENTANA

			for (int i = 0; i < longitudDelMatch; i++)
				bloque[posicionActualDeProcesoDentroDelBloque+i] = bloque[posicionMatch-posicionDeInicioDelBloque+i];

		}
	}
}

void LZP::establecerContexto(){

	if (posicionActualDeProcesoDentroDelBloque > 1){
		vectorContexto[0] = bloque[posicionActualDeProcesoDentroDelBloque-2];// o - 0
		vectorContexto[1] = bloque[posicionActualDeProcesoDentroDelBloque-1];// o - 1
	}

	else if (posicionActualDeProcesoDentroDelBloque == 0){
			vectorContexto[0] = anteultimoCaracterDelBLoqueAnterior;
			vectorContexto[1] = ultimoCaracterDelBLoqueAnterior;
		}

		else{// posicionActualDeProcesoDentroDelBloque == 1
			vectorContexto[0] = ultimoCaracterDelBLoqueAnterior;
			vectorContexto[1] = bloque[0];
		}
}

void LZP::recibirDupla() {

	establecerContexto();

	int longitudDelMatch = recibirLongitudDeMatch();
	obtenerMatch(longitudDelMatch);

	bloque[posicionActualDeProcesoDentroDelBloque+longitudDelMatch] = manejador->obtenerCaracter(bloque[posicionActualDeProcesoDentroDelBloque+longitudDelMatch-1]);

	indice->actualizar(vectorContexto[0],vectorContexto[1], posicionActualdeProceso);
	posicionActualdeProceso += longitudDelMatch+1;
	posicionActualDeProcesoDentroDelBloque = posicionActualdeProceso-posicionDeInicioDelBloque;

}

void LZP::recibirCaracterSinContexto() {
	bloque[posicionActualdeProceso] = manejador->obtenerCaracter();
	posicionActualdeProceso++;
}

void LZP::recibirCaracterConContexto1() {
	bloque[posicionActualdeProceso] = manejador->obtenerCaracter(bloque[posicionActualdeProceso-1]);;
	posicionActualdeProceso++;
}

void LZP::descomprimir(string rutaArchivo, BitHandlerInput &pbhi) {
	ofstream arch;
	arch.open(rutaArchivo.c_str(), ios::binary);
	if (arch.fail()) cout << "fail al crear " << rutaArchivo.c_str() << endl;
	arch.close();

	archivoSalida = new fstream(rutaArchivo.c_str(), ios::binary|ios::in|ios::out);

	if (archivoSalida->fail()) cout << "fail al abrir " << rutaArchivo.c_str() << endl;

	manejador->setBitHandlerInput(pbhi);  // lee los datos comprimidos

	// Recibo los dos primeros caracteres
	recibirCaracterSinContexto();
	recibirCaracterConContexto1();
	indice->actualizar(bloque[0],bloque[1], 2);
	indice->actualizar(bloque[1],bloque[2], 3);

	posicionActualDeProcesoDentroDelBloque = posicionActualdeProceso;

	for (int i = 0; i < cantidadBloques; i++) {
		cout << "\rBloque " << i + 1 << " de " << cantidadBloques << "..." << flush;
		while (posicionActualDeProcesoDentroDelBloque < TAM_BLOQUE) {
			recibirDupla();
		}

		anteultimoCaracterDelBLoqueAnterior = bloque[TAM_BLOQUE-2];
		ultimoCaracterDelBLoqueAnterior = bloque[TAM_BLOQUE-1];

		archivoSalida->seekg(posicionDeInicioDelBloque,ios::beg);
		archivoSalida->write(bloque, TAM_BLOQUE);

		if (archivoSalida->fail()) cout << "error al tratar de escribir" << endl;


		posicionDeInicioDelBloque = TAM_BLOQUE*(i+1);
		posicionActualDeProcesoDentroDelBloque = 0;

	}

	posicionActualDeProcesoDentroDelBloque = posicionActualdeProceso - posicionDeInicioDelBloque;

	while (posicionActualDeProcesoDentroDelBloque < bytesRestantes) {
		recibirDupla();
	}

	archivoSalida->seekg(posicionDeInicioDelBloque,ios::beg);
	archivoSalida->write(bloque, bytesRestantes);
	if (archivoSalida->fail()) cout << "error al tratar de escribir bloque final" << endl;


	archivoSalida->close();
	delete archivoSalida;
}
